/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import mopso.DominanceOperators;
import mopso.DominanceOperators.Dominance;
import mopso.Particle;
import mopso.Particle_CSS;
import mopso.Problem;
import mopso.Repository;

/**
 *
 * @author Guest
 */
public class Turbulence {

    private Repository repositoryOperators;
    private DominanceOperators dominanceOperator;
    private int nCallObjectiveFunction;
    private Dominance dominance;

    public Turbulence() {
        this.repositoryOperators = new Repository();
        this.dominanceOperator = new DominanceOperators();
    }

    public ArrayList<Particle> localSearch(ArrayList<Particle> externalArchive, double SIGMA, Problem problem, int nCallObjectiveFunction) {
        double[] step;
        int nParticlesTurbulence;
        double lambda1, lambda2;
        double minBound, maxBound;
        ArrayList<Integer> indexes;
        ArrayList<Particle> selectedParticles;
        Random r;
        Integer index;
        double[] positions;
        double[] velocities;

        step = new double[problem.getNDimensions()];
        this.nCallObjectiveFunction = nCallObjectiveFunction;

        for (int i = 0; i < problem.getNDimensions(); i++) {
            step[i] = SIGMA * (problem.getMaxBound(i) - problem.getMinBound(i));
        }

        nParticlesTurbulence = (int) (externalArchive.size() * 0.1);
        indexes = new ArrayList<Integer>();
        selectedParticles = new ArrayList<Particle>();
        r = new Random();

//// 10% de partículas aleatórias do arquivo externo
//        for (int i = 0; i < externalArchive.size(); i++) {
//            indexes.add(i);
//        }
//
//        int k = nParticlesTurbulence;
//
//        while (k > 0) {
//            index = indexes.get(r.nextInt(indexes.size()));
//            indexes.remove(index);
//            selectedParticles.add(((Particle) ((Particle) externalArchive.get(index)).clone()));
//            k--;
//        }

// 10% de partículas menos povoadas do arquivo externo

        Collections.sort(externalArchive, Collections.reverseOrder());

        for (int i = 0; i < nParticlesTurbulence; i++) {
            selectedParticles.add(((Particle) ((Particle) externalArchive.get(i)).clone()));
        }

        for (int j = 0; j < selectedParticles.size(); j++) {

            if (this.nCallObjectiveFunction < Constants.NCALLOBJECTIVEFUNCTION) {
                positions = selectedParticles.get(j).getParticleData().getPositions();
                velocities = selectedParticles.get(j).getParticleData().getVelocities();

                for (int i = 0; i < problem.getNDimensions(); i++) {
                    minBound = problem.getMinBound(i);
                    maxBound = problem.getMaxBound(i);
                    lambda1 = Math.random();
                    lambda2 = Math.random();

                    if (lambda1 > 0.5) {
                        positions[i] = positions[i] + (lambda2 * step[i]);
                    } else {
                        positions[i] = positions[i] - (lambda2 * step[i]);
                    }

                    if (positions[i] < minBound) {
                        positions[i] = minBound;
                        velocities[i] *= -1;
                    } else if (positions[i] > maxBound) {
                        positions[i] = maxBound;
                        velocities[i] *= -1;
                    }
                }

                selectedParticles.get(j).getParticleData().setPositions(positions);
                selectedParticles.get(j).getParticleData().setVelocities(velocities);

                selectedParticles.get(j).calculateFitness();
                this.nCallObjectiveFunction++;

                dominance = dominanceOperator.verifyDominace(selectedParticles.get(j), externalArchive.get(j), problem);

                if ((dominance == Dominance.DOMINATES) /* || ( dominance == Dominance.CO_DOMINANCE )*/) {
                    externalArchive.set(j, selectedParticles.get(j));
                }
            }
        }

        externalArchive = repositoryOperators.removeDominatedParticlesExternalArchive(externalArchive, problem);

//        externalArchive = repositoryOperators.updateExternalArchive(externalArchive, selectedParticles, problem);

        return externalArchive;
    }

    public ArrayList<Particle> turbulence(ArrayList<Particle> swarm, double mutationPercentualParticles, double mutationPercentualDimensions, double standardDeviationMutationGaussian, double initialSDMutationGaussian, double finalSDMutationGaussian, int nIterations, Problem problem) {
        int nParticlesTurbulence = (int) (swarm.size() * mutationPercentualParticles);
        ArrayList<Integer> indexes = new ArrayList<Integer>();
        Random r = new Random();
        Integer index;

        standardDeviationMutationGaussian = standardDeviationMutationGaussian - ((initialSDMutationGaussian - finalSDMutationGaussian) / nIterations);

        for (int i = 0; i < swarm.size(); i++) {
            indexes.add(i);
        }

        int k = nParticlesTurbulence;

        while (k > 0) {
            index = indexes.get(r.nextInt(indexes.size()));
            indexes.remove(index);
            swarm.set(index, this.applyTurbulence(swarm.get(index), problem, mutationPercentualDimensions, standardDeviationMutationGaussian));
            k--;
        }
        return swarm;
    }

    public ArrayList<Particle_CSS> turbulence_CSS(ArrayList<Particle_CSS> swarm, Problem problem, double STANDARDDEVIATIONMUTATIONGAUSSIAN) {
        ArrayList<Integer> indexes;
        Random r;
        Integer index;

        indexes = new ArrayList<Integer>();
        r = new Random();

        for (int i = 0; i < swarm.size(); i++) {
            indexes.add(i);
        }

        int k = r.nextInt(swarm.size());

        while (k > 0) {
            index = indexes.get(r.nextInt(indexes.size()));
            indexes.remove(index);

            swarm.get(index).getParticleData().setVelocities(this.applyTurbulence(swarm.get(index).getParticleData().getVelocities(), problem, STANDARDDEVIATIONMUTATIONGAUSSIAN));

            k--;
        }

        return swarm;
    }

    public ArrayList<Particle> turbulence_MOPSO(ArrayList<Particle> swarm, Problem problem, int currentNCallObjectiveFunction, int nCallObjectiveFunction, double mutationPercentual) {
        int wichDim;
        double mutRange;
        double ub, lb;
        Random r = new Random();
        Random realRandom = new Random();

        for (int i = 0; i < swarm.size(); i++) {
            if (this.flip(Math.pow((1.0 - ((double) currentNCallObjectiveFunction / (double) nCallObjectiveFunction)), (5.0 / mutationPercentual)))) {
                wichDim = r.nextInt(problem.getNDimensions());
                mutRange = (problem.getMaxBound(wichDim) - problem.getMinBound(wichDim) * Math.pow((1.0 - ((double) currentNCallObjectiveFunction / (double) nCallObjectiveFunction)), (5.0 / mutationPercentual)));
                ub = swarm.get(i).getParticleData().getPositions()[wichDim] + mutRange;
                lb = swarm.get(i).getParticleData().getPositions()[wichDim] - mutRange;

                if (lb < problem.getMinBound(wichDim)) {
                    lb = problem.getMinBound(wichDim);
                }

                if (ub < problem.getMaxBound(wichDim)) {
                    ub = problem.getMaxBound(wichDim);
                }

                swarm.get(i).getParticleData().setPositions(lb + (realRandom.nextDouble() * (ub - lb)), wichDim);
            }
        }
        return swarm;
    }

    public ArrayList<Particle> turbulence_CSS_forParticle(ArrayList<Particle> swarm, Problem problem, double STANDARDDEVIATIONMUTATIONGAUSSIAN) {
        ArrayList<Integer> indexes;
        Random r;
        Integer index;

        indexes = new ArrayList<Integer>();
        r = new Random();

        for (int i = 0; i < swarm.size(); i++) {
            indexes.add(i);
        }

        int k = r.nextInt(swarm.size());

        while (k > 0) {
            index = indexes.get(r.nextInt(indexes.size()));
            indexes.remove(index);

            swarm.get(index).getParticleData().setVelocities(this.applyTurbulence(swarm.get(index).getParticleData().getVelocities(), problem, STANDARDDEVIATIONMUTATIONGAUSSIAN));

            k--;
        }

        return swarm;
    }

    private double[] applyTurbulence(double[] velocities, Problem problem, double STANDARDDEVIATIONMUTATIONGAUSSIAN) {
        ArrayList<Integer> indexes = new ArrayList<Integer>();
        Random r = new Random();
        Integer index;

        for (int i = 0; i < problem.getNDimensions(); i++) {
            indexes.add(i);
        }

        int k = r.nextInt(velocities.length);

        while (k > 0) {
            index = indexes.get(r.nextInt(indexes.size()));
            indexes.remove(index);
            velocities[index] = this.applyGaussianMutation(velocities[index], STANDARDDEVIATIONMUTATIONGAUSSIAN);
            k--;
        }
        return velocities;
    }

    private Particle applyTurbulence(Particle particle, Problem problem, double mutationPercentualDimensions, double standardDeviationMutationGaussian) {
        int nDimensionsTurbulence = (int) (problem.getNDimensions() * mutationPercentualDimensions);
        double[] velocities = particle.getParticleData().getVelocities().clone();
        ArrayList<Integer> indexes = new ArrayList<Integer>();
        Random r = new Random();
        Integer index;

        for (int i = 0; i < problem.getNDimensions(); i++) {
            indexes.add(i);
        }

        int k = nDimensionsTurbulence;

        while (k > 0) {
            index = indexes.get(r.nextInt(indexes.size()));
            indexes.remove(index);
            velocities[index] = this.applyGaussianMutation(velocities[index], standardDeviationMutationGaussian);
            k--;
        }
        return particle;
    }

    private double applyGaussianMutation(double value, double standardDeviationMutationGaussian) {
//        double x;
//        double newValue;

        Random r = new Random();

        value += (standardDeviationMutationGaussian * r.nextGaussian());

        return value;
//
//        x = ( Math.abs( value ) - standardDeviationMutationGaussian ) +
//                ( Math.random() * ( standardDeviationMutationGaussian * 2 ) );
//
//        if ( value != 0.0 ) {
//            newValue = Math.signum(value) * Math.exp(-(Math.pow(x - Math.abs( value ), 2) / (2 * Math.pow(standardDeviationMutationGaussian, 2)))) / (standardDeviationMutationGaussian * Math.sqrt(2 * Math.PI));
//        }
//
//        else {
//            newValue = Math.exp(-(Math.pow(x - Math.abs( value ), 2) / (2 * Math.pow(standardDeviationMutationGaussian, 2)))) / (standardDeviationMutationGaussian * Math.sqrt(2 * Math.PI));
//            if ( Math.random() < 0.5 ) {
//                newValue *= -1;
//            }
//        }

//        return newValue;
    }

    /**
     * @return the nCallObjectiveFunction
     */
    public int getNCallObjectiveFunction() {
        return nCallObjectiveFunction;
    }

    /**
     * @param nCallObjectiveFunction the nCallObjectiveFunction to set
     */
    public void setNCallObjectiveFunction(int nCallObjectiveFunction) {
        this.nCallObjectiveFunction = nCallObjectiveFunction;
    }

    private boolean flip(double value) {
        if (Math.random() <= value) {
            return true;
        }

        return false;
    }
}
